﻿using Gargoyle.Roton;
using Gargoyle.ZZT.File;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace Gargoyle.ZZT
{
    public partial class Game : Roton.Environment
    {
		/// <summary>
		/// Execute Act code for a thing.
		/// </summary>
		/// <param name="index">Index of the thing to run code for.</param>
        internal void Act(int index)
        {
            Element element = GetElementAt(Things[index].Location);

            if (element.ActCode != null)
                element.ActCode(this, index);
        }

		/// <summary>
		/// Make a thing 'attack' a tile, sacrificing itself in the process.
		/// </summary>
		/// <param name="index">Index of the thing that is attacking.</param>
		/// <param name="target">Location to attack.</param>
        internal void Attack(int index, Location target)
        {
            Element targetElement = GetElementAt(target);

            if ((index == 0) && (Info.EnergyCycles > 0))
            {
                Info.Score += targetElement.Points;
                UpdateStatus();
            }
            else
            {
                HarmThing(index);
            }

            if ((index > 0) && (index <= State.ActIndex))
            {
                State.ActIndex--;
            }

            if ((targetElement.Id == ElementID.Player) && (Info.EnergyCycles > 0))
            {
                Element sourceElement = GetElementAt(Things[index].Location);
                Info.Score += sourceElement.Points;
                UpdateStatus();
            }
            else
            {
                DestroyAt(target);
                PlaySound(2, Sounds.EnemySuicide);
            }
        }

		/// <summary>
		/// Destroy a tile. If a thing is present, this will remove it, otherwise it will be set to floor/empty.
		/// </summary>
		/// <param name="location">Location of tile to destroy.</param>
        internal void DestroyAt(Location location)
        {
            int index = GetThingIndexAt(location);
            if (index >= 0)
            {
                HarmThing(index);
            }
            else
            {
                RemoveItem(location);
            }
        }

		/// <summary>
		/// Process events that happen when a board is entered.
		/// </summary>
        internal void EnterBoard()
        {
            CurrentBoard.Enter = Things[0].Location;
            State.TimeOnBoard = 0;
            Info.TimePassed = 0;
        }

		/// <summary>
		/// Execute one cycle for all things on the board.
		/// </summary>
        private void ExecuteCycle()
        {
            if (State.ActIndex >= Things.Count)
            {
                State.Tick++;
                if (State.Tick > 0x1A4)
                {
                    State.Tick = 1;
                }
                State.ActIndex = 0;
                ReadInput();
            }

            if (OOPState != null)
            {
                State.ActIndex = OOPState.Index;
            }

            while (!((State.ActIndex >= Things.Count) || (DisplayBusy) || (State.BreakGameLoop) || (State.Paused)))
            {
                Thing thing = Things[State.ActIndex];
                if (thing.Cycle != 0)
                {
                    if ((State.ActIndex % thing.Cycle) == (State.Tick % thing.Cycle))
                    {
                        Act(State.ActIndex);
                    }
                }
                if (!DisplayBusy)
                {
                    State.ActIndex++;
                }
            }
        }

		/// <summary>
		/// Main execution code.
		/// </summary>
        private void ExecuteMain()
        {
            if (State != null)
            {
				if (State.InitGameLoop)
				{
					State.InitGameLoop = false;
					RedrawStatus();

					if (State.InitGame)
					{
						if (!ShownAbout)
							ShowAbout();
						// normally we load the game here, however
						// it should have been loaded already
						State.InitGame = false;
					}

					State.WaitTime = State.Speed << 1;
					if (State.WaitTime == 0)
						State.WaitTime = 1;
					State.BreakGameLoop = false;
					State.Tick = RandomNumber(100);
					State.ActIndex = Things.Count;
					SetTileAt(Things[0].Location, new Tile(Elements.IndexOf(State.PlayerElement), State.PlayerElement.DefaultColor));
				}

                if (State.Paused)
                {
                    ExecutePaused();
                }
                else if ((State.ActIndex < Things.Count) || (Tick % State.WaitTime == 0))
                {
                    ExecuteCycle();
                    UpdateScreenInternal = true;

                    if (Display != null)
                    {
                        UpdateDisplay();

                        int messengerIndex = GetThingIndexAt(new Location(0, 0));
                        if (messengerIndex > 0 && CurrentBoard.Message != null)
                        {
                            byte color = (byte)(((Things[messengerIndex].P2 + 1) % 7) + 9);
                            Display.DrawMessage(CurrentBoard.Message, color);
                        }
                    }
                }

                if ((!State.Paused) && (!DisplayBusy))
                {
                    State.TimeOnBoard++;
                }

				if (!DisplayBusy && State.BreakGameLoop)
				{
					if (State.PlayerElement.Id == ElementID.Player)
					{
						if (Info.Health <= 0)
						{
							// show high scores
						}
					}
					SetTileAt(Things[0].Location, new Tile(Elements.IndexOf(ElementID.Player), Elements[ElementID.Player].DefaultColor));

					if (State.TitleScreen)
					{
						switch (State.KeyPressed)
						{
							case Keys.W:
								ShowLoadWorld();
								break;
							case Keys.A:
								ShowAbout();
								break;
							case Keys.E:
								// load editor
								break;
							case Keys.S:
								if (Parameters.EnableGameSpeedChange)
								{
									DisplayChoice(SelectionParameter.GameSpeed, State.Speed);
								}
								break;
							case Keys.R:
								ShowRestoreGame();
								break;
							case Keys.H:
								// high scores
								break;
							case Keys.Escape:
							case Keys.Q:
								StopSound();
								ShowQuitZZT();
								break;
							default:
								if (State.KeyPressed == Parameters.PlayKey)
								{
									if (Info.SavedGame != 0)
									{
										Load(Info.Name);
									}
                                    StopSound();
									SetGameMode();
								}
								break;
						}
					}
					else
					{
						SetTitleMode();
					}
					State.BreakGameLoop = false;
					State.InitGameLoop = true;
				}
			}
        }

		/// <summary>
		/// Paused execution code.
		/// </summary>
        private void ExecutePaused()
        {
            Element playerElement = Elements[ElementID.Player];

            ReadInput();

            if ((Tick % 20) == 0)
            {
                State.PausedBlink = !State.PausedBlink;
                if (State.PausedBlink)
                {
                    BoardVideo.SetChar(Things[0].Location.ToPoint(), new AnsiChar(playerElement.DefaultChar, playerElement.DefaultColor));
                }
                else
                {
                    Thing player = Things[0];
                    if (GetElementAt(player.Location).Id == ElementID.Player)
                        BoardVideo.SetChar(player.Location.ToPoint(), new AnsiChar(0x20, 0x00));
                    else
                        BoardVideo.SetChar(player.Location.ToPoint(), new AnsiChar(GetElementAt(player.Location).DefaultChar, GetColorAt(player.Location)));
                }
                UpdateScreenInternal = true;
            }

            if (State.KeyPressed == Keys.Escape)
            {
				State.BreakGameLoop = true;
                ShowEndGame();
            }

            if ((State.KeyVector.X != 0) || (State.KeyVector.Y != 0))
            {
                Thing player = Things[0];
                Location targetLocation = new Location((byte)(State.KeyVector.X + player.Location.X), (byte)(State.KeyVector.Y + player.Location.Y));
                Interact(targetLocation, 0, State.KeyVector);
                UpdateScreenInternal = true;
            }

            if ((State.KeyVector.X != 0) || (State.KeyVector.Y != 0))
            {
                Thing player = Things[0];
                Location targetLocation = new Location((byte)(State.KeyVector.X + player.Location.X), (byte)(State.KeyVector.Y + player.Location.Y));
                if (GetElementAt(targetLocation).Floor)
                {
                    if (Parameters.BuggyPassages)
                    {
                        if (GetElementAt(player.Location).Id == ElementID.Player)
                        {
                            MoveThing(0, targetLocation);
                        }
                        else
                        {
                            SetTileAt(targetLocation, new Tile(Elements.IndexOf(ElementID.Player), Elements[ElementID.Player].DefaultColor));
                            UpdateAt(player.Location);
                            player.Location = targetLocation;
                            UpdateAt(targetLocation);
                            Radius(player.Location, RadiusType.Update);
                            Radius(new Location((byte)(player.Location.X - State.KeyVector.X), (byte)(player.Location.Y - State.KeyVector.Y)), RadiusType.Update);
                        }
                    }
                    else
                    {
                        if (GetElementAt(player.Location).Id == ElementID.Player)
                        {
                            MoveThing(0, targetLocation);
                        }
                        else
                        {
                            UpdateAt(player.Location);
                            player.Location = targetLocation;
                            player.UnderTile = GetTileAt(targetLocation);
                            SetTileAt(targetLocation, new Tile(Elements.IndexOf(ElementID.Player), Elements[ElementID.Player].DefaultColor));
                            UpdateAt(targetLocation);
                            Radius(player.Location, RadiusType.Update);
                            Radius(new Location((byte)(player.Location.X - State.KeyVector.X), (byte)(player.Location.Y - State.KeyVector.Y)), RadiusType.Update);
                        }
                    }
                    State.Paused = false;
                    State.Tick = Random.Next(100);
                    State.ActIndex = Things.Count + 1;
                    Info.SavedGame = 1;
                    RedrawStatus();
                }
            }

            UpdateDisplay();
        }

		/// <summary>
		/// Attempt to destroy a thing. If it is the player, it is damaged instead of destroyed.
		/// </summary>
		/// <param name="index">The index of the thing to destroy.</param>
        internal void HarmThing(int index)
        {
            Thing thing = Things[index];
            Element element = GetElementAt(thing.Location);

            if (index == 0)
            {
                if (Info.Health > 0)
                {
                    Info.Health -= 10;
                    UpdateStatus();
                    SetMessageInternal(0x64, Parameters.OuchMessage, 0);

                    byte oldColor = GetColorAt(thing.Location);
                    byte color = oldColor;
                    color = (byte)((element.DefaultColor & 0x0F) | 0x70);
                    SetColorAt(thing.Location, color);
                    UpdateAt(thing.Location);
                    SetColorAt(thing.Location, oldColor);

                    if (Info.Health > 0)
                    {
                        Info.TimePassed = 0;
                        State.TimeOnBoard = 0;
                        if (CurrentBoard.RestartOnZap != 0)
                        {
                            PlaySound(4, Sounds.TimeOut);
                            SetElementAt(thing.Location, Elements[ElementID.Empty]);
                            UpdateAt(thing.Location);
                            Location oldLocation = thing.Location;
                            thing.Location = CurrentBoard.Enter;
                            Radius(oldLocation, RadiusType.Update);
                            Radius(thing.Location, RadiusType.Update);
                            State.Paused = true;
                        }
                        PlaySound(4, Sounds.Ouch);
                    }
                    else
                    {
                        PlaySound(5, Sounds.GameOver);
                    }
                }
            }
            else
            {
                switch (element.Id)
                {
                    case ElementID.Bullet:
                        PlaySound(3, Sounds.BulletDie);
                        break;
                    case ElementID.Object:
                        break;
                    default:
                        PlaySound(3, Sounds.EnemyDie);
                        break;
                }
                RemoveThing(index);
            }
        }

		/// <summary>
		/// Execute Interact code.
		/// </summary>
		/// <param name="location">Location where the player will be interacting.</param>
		/// <param name="index">The index of the thing that will be interacting.</param>
		/// <param name="vector">The vector that the player will use to interact.</param>
        internal void Interact(Location location, int index, Vector vector)
        {
            Element element = GetElementAt(location);

            if (element.InteractCode != null)
                element.InteractCode(this, location, index, vector);
        }

		/// <summary>
		/// Move a tile.
		/// </summary>
		/// <param name="location">Source location.</param>
		/// <param name="target">Target location.</param>
        internal void Move(Location location, Location target)
        {
            int index = GetThingIndexAt(location);

            if (index < 0)
            {
                SetTileAt(target, GetTileAt(location));
                UpdateAt(target);
                RemoveItem(location);
                UpdateAt(location);
            }
            else
            {
                MoveThing(index, target);
            }
        }

		/// <summary>
		/// Move a thing, preserving under-tile properties.
		/// </summary>
		/// <param name="index">Index of the thing to move.</param>
		/// <param name="location">The location to move the thing.</param>
        internal void MoveThing(int index, Location location)
        {
            Thing thing = Things[index];
            Location sourceLocation = thing.Location.Clone();
            Location targetLocation = location.Clone();
            Tile sourceTile = GetTileAt(sourceLocation);
            Tile targetTile = GetTileAt(location);

            SetTileAt(sourceLocation, thing.UnderTile);
            SetTileAt(targetLocation, sourceTile);
            thing.UnderTile = targetTile;
            if ((Parameters.ForcePlayerColor) && (Elements[sourceTile.Element].Id == ElementID.Player))
            {
                SetColorAt(targetLocation, sourceTile.Color);
            }
            else if (Elements[targetTile.Element].Id == ElementID.Empty)
            {
                SetColorAt(targetLocation, (byte)(sourceTile.Color & 0xF));
            }
            else
            {
                SetColorAt(targetLocation, (byte)((sourceTile.Color & 0xF) | (targetTile.Color & 0x70)));
            }
            thing.Location = targetLocation;
            UpdateAt(sourceLocation);
            UpdateAt(targetLocation);

            if ((index == 0) && (CurrentBoard.Dark != 0))
            {
                int squareDistanceX = targetLocation.X - sourceLocation.X;
                int squareDistanceY = targetLocation.Y - sourceLocation.Y;
                if ((squareDistanceX * squareDistanceX) + (squareDistanceY * squareDistanceY) == 1)
                {
                    for (int i = targetLocation.X - 11; i <= targetLocation.X + 11; i++)
                    {
                        if (i >= 0)
                        {
                            for (int j = targetLocation.Y - 8; j <= targetLocation.Y + 8; j++)
                            {
                                if (j >= 0)
                                {
                                    bool updateTile;
                                    Location glowLocation = new Location((byte)i, (byte)j);
                                    updateTile = WithinRadius(targetLocation, glowLocation);
                                    updateTile ^= WithinRadius(sourceLocation, glowLocation);
                                    if (updateTile)
                                        UpdateAt(glowLocation);
                                }
                            }
                        }
                    }
                }
                else
                {
                    Radius(sourceLocation, RadiusType.Update);
                    Radius(targetLocation, RadiusType.Update);
                }
            }
        }

		/// <summary>
		/// Attempt to push a tile in a direction. Using a zero vector will crash the game.
		/// </summary>
		/// <param name="location">Location of the tile to push.</param>
		/// <param name="vector">Direction in which to push the tile.</param>
        internal void Push(Location location, Vector vector)
        {
            Element element = GetElementAt(location);
            Location target = location.Add(vector);

            if ((element.Pushable) ||
                ((vector.X == 0) && (element.Id == ElementID.SliderNS)) ||
                ((vector.Y == 0) && (element.Id == ElementID.SliderEW)))
            {
                if (vector.IsIdle)
                    throw new StackOverflowException("Cannot push with an Idle vector.");

                element = GetElementAt(target);
                if (element.Id == ElementID.Transporter)
                    PushTransporter(location, vector);
                else if (element.Id != ElementID.Empty)
                    Push(target, vector);

                element = GetElementAt(target);
                if (!element.Floor)
                {
                    if ((element.Destructible) && (element.Id != ElementID.Player))
                    {
                        DestroyAt(target);
                        element = GetElementAt(target);
                    }
                }

                if (element.Floor)
                {
                    Move(location, target);
                }
            }
        }

		/// <summary>
		/// Push through a transporter.
		/// </summary>
		/// <param name="location">The location of the transporter.</param>
		/// <param name="vector">The direction in which to push.</param>
        internal void PushTransporter(Location location, Vector vector)
        {
            Thing thing = GetThingAt(location.Add(vector));

            if (thing.Vector == vector)
            {
                Location search = thing.Location;
                Location target = new Location();
                bool ended = false;
                bool success = true;

                while (!ended)
                {
                    search = search.Add(vector);
                    Element element = GetElementAt(search);
                    if (element.Id == ElementID.BoardEdge)
                    {
                        ended = true;
                    }
                    else
                    {
                        if (success)
                        {
                            success = false;
                            if (!element.Floor)
                            {
                                Push(search, vector);
                                element = GetElementAt(search);
                            }
                            if (element.Floor)
                            {
                                ended = true;
                                target = search;
                            }
                            else
                            {
                                target.X = 0;
                            }
                        }
                    }
                    if (element.Id == ElementID.Transporter)
                    {
                        if (GetThingAt(search).Vector == vector.Opposite())
                        {
                            success = true;
                        }
                    }
                }
                if (target.X > 0)
                {
                    Move(thing.Location.Subtract(vector), target);
                    PlaySound(3, Sounds.Transporter);
                }
            }
        }

		/// <summary>
		/// Update the screen in a radius.
		/// </summary>
		/// <param name="location">The center of the circle.</param>
		/// <param name="radiusType">The type of update to perform.</param>
        internal void Radius(Location location, RadiusType radiusType)
        {
            RadiusFunction radiusFunction = RadiusFunctionUpdate;

            switch (radiusType)
            {
                case RadiusType.Explode:
                    radiusFunction = RadiusFunctionExplode;
                    break;
                case RadiusType.Cleanup:
                    radiusFunction = RadiusFunctionCleanup;
                    break;
            }

            for (int i = location.X - 7; i <= location.X + 7; i++)
            {
                if (i >= 0)
                {
                    for (int j = location.Y - 4; j <= location.Y + 4; j++)
                    {
                        if (j >= 0)
                        {
                            Location glowLocation = new Location((byte)i, (byte)j);
                            radiusFunction(location, glowLocation);
                            UpdateAt(glowLocation);
                        }
                    }
                }
            }
        }

		/// <summary>
		/// Update the screen in a radius.
		/// </summary>
		private void RadiusFunctionUpdate(Location center, Location location)
        {
            // leave this empty
        }

		/// <summary>
		/// Clean up an explosion of breakables in a radius.
		/// </summary>
		private void RadiusFunctionCleanup(Location center, Location location)
        {
            if (WithinRadius(center, location))
            {
                if (GetElementAt(location).Id == ElementID.Breakable)
                {
                    SetElementAt(location, Elements[ElementID.Empty]);
                }
            }
        }

		/// <summary>
		/// Perform an explosion of breakable walls in a radius.
		/// </summary>
        private void RadiusFunctionExplode(Location center, Location location)
        {
            if (WithinRadius(center, location))
            {
                Element bombedElement = GetElementAt(location);
                int thingIndex = GetThingIndexAt(location);
                if ((thingIndex > 0) && (bombedElement.CodeText != ""))
                {
                    SendLabel(-thingIndex, Parameters.BombedLabel, false);
                }
                if ((bombedElement.Destructible) || (bombedElement.Id == ElementID.Star))
                {
                    DestroyAt(location);
                    bombedElement = GetElementAt(location);
                }
                if ((bombedElement.Id == ElementID.Empty) || (bombedElement.Id == ElementID.Breakable))
                {
                    SetTileAt(location, new Tile(Elements.IndexOf(ElementID.Breakable), (byte)(Random.Next(7) + 9)));
                }
            }
        }

		/// <summary>
		/// Set the tile to either empty or floor, depending on Parameters.PickupToFloor.
		/// </summary>
        internal void RemoveItem(Location location)
        {
            if (Parameters.PickupToFloor)
            {
                RemoveItemFloor(location);
            }
            else
            {
                SetElementAt(location, Elements[ElementID.Empty]);
            }
            UpdateAt(location);
        }

		/// <summary>
		/// Set a tile to floor. Floor color is determined by surrounding floor tiles.
		/// </summary>
		private void RemoveItemFloor(Location location)
        {
            Tile result = new Tile(Elements.IndexOf(ElementID.Floor), 0x00);
            bool finished = false;

            for (int i = 0; i < 4; i++)
            {
                Vector targetVector = Parameters.CardinalVectors[i];
                Location targetLocation = new Location((byte)(location.X + targetVector.X), (byte)(location.Y + targetVector.Y));
                Tile adjacentTile = GetTileAt(targetLocation);
                if (Elements[adjacentTile.Element].Cycle >= 0)
                    adjacentTile = GetThingAt(targetLocation).UnderTile;
                switch (Elements[adjacentTile.Element].Id)
                {
                    case ElementID.Empty:
                    case ElementID.SliderNS:
                    case ElementID.SliderEW:
                    case ElementID.Boulder:
                        finished = true;
                        result.Color = 0;
                        break;
                    case ElementID.Floor:
                        result.Color = adjacentTile.Color;
                        break;
                }
                if (finished)
                {
                    break;
                }
            }

            if (result.Color == 0)
            {
                result.Element = Elements.IndexOf(ElementID.Empty);
            }
            SetTileAt(location, result);
        }

		/// <summary>
		/// Remove a thing. This preserves under-tile properties.
		/// </summary>
		/// <param name="index">Index of the thing to remove.</param>
        internal void RemoveThing(int index)
        {
            Thing thing = Things[index];

            if ((index > 0) && (index < Things.Count))
            {
                thing.Code = null;
                thing.Length = 0;
                if (index < State.ActIndex)
                    State.ActIndex--;
                SetTileAt(thing.Location, thing.UnderTile);
                UpdateAt(thing.Location);
                Things.RemoveAt(index);
            }
        }

		/// <summary>
		/// Generate a random direction with 2x weight on horizontal movement.
		/// </summary>
        internal Vector Rnd()
        {
            Vector result = new Vector((Int16)(Random.Next(3) - 1), 0);

            if (result.X == 0)
            {
                result.Y = (Int16)((Random.Next(2) << 1) - 1);
            }

            return result;
        }

		/// <summary>
		/// Generate a random direction between North and East.
		/// </summary>
		internal Vector RndNE()
        {
            if (Random.Next(2) == 0)
                return Vector.North();
            return Vector.East();
        }

		/// <summary>
		/// Generate a random direction between North and South.
		/// </summary>
		internal Vector RndNS()
        {
            if (Random.Next(2) == 0)
                return Vector.North();
            return Vector.South();
        }

		/// <summary>
		/// Generate a random direction perpendicular to the given direction.
		/// </summary>
		internal Vector RndP(Vector source)
        {
            if (Random.Next(2) == 0)
                return source.Clockwise();
            return source.CounterClockwise();
        }

		/// <summary>
		/// Get the vector that points to the player. If energized, the vector will be opposite.
		/// </summary>
		internal Vector Seek(Location source)
        {
            Thing player = Things[0];
            Vector result = new Vector(0, 0);

            if ((player.Location.Y == source.Y) || (Random.Next(2) == 1))
            {
                result = new Vector((Int16)Polarity(player.Location.X - source.X), 0);
            }

            if (result.X == 0)
            {
                result.Y = (Int16)Polarity(player.Location.Y - source.Y);
            }

            if (Info.EnergyCycles > 0)
            {
                result = result.Opposite();
            }

            return result;
        }

		/// <summary>
		/// Spawn a projectile.
		/// </summary>
		/// <param name="element">Element of the projectile.</param>
		/// <param name="location">Location to spawn the projectile at.</param>
		/// <param name="vector">Vector to set the projectile to.</param>
		/// <param name="enemyOwned">If true, this projectile was created by an enemy.</param>
		/// <returns></returns>
        internal bool SpawnProjectile(Element element, Location location, Vector vector, bool enemyOwned)
        {
            Location target = location.Add(vector);
            Element targetElement = GetElementAt(target);
            bool success = false;

            if (targetElement.Floor || (targetElement.Id == ElementID.Water))
            {
                SpawnThing(target, element, element.DefaultColor, element.Cycle, null);
                Thing projectileThing = Things[Things.Count - 1];

                if (enemyOwned)
                    projectileThing.P1 = 1;
                else
                    projectileThing.P1 = 0;

                projectileThing.Vector.CopyFrom(vector);
                projectileThing.P2 = 0x64;
                success = true;
            }
            else if ((targetElement.Id == ElementID.Breakable) || (targetElement.Destructible && (((targetElement.Id == ElementID.Player) && (Info.EnergyCycles == 0)) || (!enemyOwned))))
            {
                DestroyAt(target);
                PlaySound(2, Sounds.BulletDie);
                success = true;
            }

            return success;
        }

		/// <summary>
		/// Spawn a thing.
		/// </summary>
		/// <param name="location">Location to spawn the thing at.</param>
		/// <param name="element">Element of the thing to spawn.</param>
		/// <param name="color">Color of the thing to spawn.</param>
		/// <param name="cycle">Cycle of the thing to spawn.</param>
		/// <param name="template">If not null, this will be used as a template to create a new thing.</param>
        internal void SpawnThing(Location location, Element element, byte color, Int16 cycle, Thing template)
        {
            if (Things.Count < Things.Capacity)
            {
                Thing thing = new Thing(Heap);

                if (template != null)
                {
                    thing.Vector.CopyFrom(template.Vector);
                    thing.P1 = template.P1;
                    thing.P2 = template.P2;
                    thing.P3 = template.P3;
                    thing.Follower = template.Follower;
                    thing.Leader = template.Leader;
                    thing.Code = template.Code;
                    thing.Length = template.Length;
                }
                else
                {
                    thing.Vector.CopyFrom(new Vector(0, 0));
                    thing.P1 = 0;
                    thing.P2 = 0;
                    thing.P3 = 0;
                    thing.Follower = -1;
                    thing.Leader = -1;
                    thing.Code = null;
                    thing.Length = 0;
                }
                thing.Location = location;
                thing.Cycle = cycle;
                thing.Instruction = 0;
                thing.UnderTile = GetTileAt(location);
                Things.Add(thing);

                if (GetElementAt(location).EditorFloor)
                    SetColorAt(location, (byte)((GetColorAt(location) & 0x70) | (color & 0x0F)));
                else
                    SetColorAt(location, color);

                SetElementAt(location, element);
                UpdateAt(location);
            }
        }

		/// <summary>
		/// Determines if a location is within the board. This includes the border.
		/// </summary>
		/// <param name="location">Location to check.</param>
		/// <returns></returns>
        internal bool WithinBoard(Location location)
        {
            return (location.X >= 0 && location.X < Size.Width && location.Y >= 0 && location.Y < Size.Height);
        }

		/// <summary>
		/// Determines if a location is within a radius.
		/// </summary>
		/// <param name="center">Center location of the radius.</param>
		/// <param name="location">Location to check.</param>
		/// <returns></returns>
        internal bool WithinRadius(Location center, Location location)
        {
            Thing player = Things[0];
            int distanceX = center.X - location.X;
            int distanceY = center.Y - location.Y;
            return ((distanceX * distanceX) + (distanceY * distanceY * 2) < 50);
        }
    }
}
